<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2020/7/21
 * Time: 16:44
 */

namespace app\service;


use Redis\Redis;
use think\Db;

class RedPacketService
{
    static $MemberCode;
    static $MemberLevel;

    function __construct($member_code = "", $member_level = "")
    {
        self::$MemberCode = $member_code;
        self::$MemberLevel = $member_level;
    }

    function demo()
    {
        $redis = new Redis();
        $redis->lPop('key1');# lPop() 移除并返回列表的第一个元素
        $redis->rPop('k2'); # rPop() 移除并返回列表的最后一个元素
    }

    /**
     * @return bool
     * @context 初始化用户红包列表
     */
    function init()
    {
        $redis = new Redis();
        $_init = $redis->hGetJson("redpack_init", self::$MemberCode);
        if (empty($_init)) {
            # 每1个小时初始化用户红包
            $list = $redis->hGet("redpack_member" . self::$MemberCode);
            if (!empty($list)) {
                foreach ($list as $k => $v) {
                    $v = json_decode($v, true);
                    if (key_exists("expiration", $v) and $v['expiration'] <= date("Y-m-d H:i:s")) {
                        unset($list[$k]);
                        continue;
                    }
                    $v = json_encode($v);
                    $redis->hSet("redpack_member" . self::$MemberCode, $k, $v);
                }
            }
        }
        return true;
    }

    /**
     * @return array
     * @context 返回用户红包列表
     */
    public function memberRedPacketList($redpacket_id)
    {
        $redis = new Redis();
        $list = $redis->hGet("redpack_member" . self::$MemberCode, $redpacket_id);
        if (!empty($list)) {
            $list = json_decode($list, true);
            $data = $redis->hGetJson('sendRedPacketInfo', strval($redpacket_id));
            return array("status" => true, 'list' => $list, "data" => $data);
        } else {
            return array("status" => false);
        }
    }

    /**
     * @return array
     * @context 返回用户红包列表的最后一项
     */
    public function memberRedPacketListInfo()
    {
        RedPacketService::init();
        $redis = new Redis();
        $list = $redis->hGet("redpack_member" . self::$MemberCode);
        if (!empty($list)) {
            $end = end($list);
            $list = json_decode($end, true);
            $data = $redis->hGetJson('sendRedPacketInfo', strval($list['redpacket_id']));
            return array("status" => true, 'list' => $list, "data" => $data);
        } else {
            return array("status" => false);
        }
    }

    /**
     * @param $param 红包数据 包含 红包信息  过期时间
     * @return bool
     * @context 单个添加用户红包列表
     */
    function addMemberRedPacket($member_code, $param)
    {
        $redis = new Redis();
        $redis->hSet("redpack_member" . $member_code, $param['redpacket_id'], json_encode($param));
        return true;
    }

    /**
     * @param $param
     * @return bool
     * @context 批量添加用户红包列表
     */
    private function addMemberRedPacketAll($param)
    {
        foreach ($param as $k => $v) {
            $code = $v['member_code'];
            unset($v['member_code']);
            RedPacketService::addMemberRedPacket($code, $param);
        }
        return true;
    }

    /**
     * @param $redpack_id
     * @param $num
     * @param $price
     * @return bool
     * @context 将红包拆分成列表
     */
    function redPacketList($redpack_id, $num, $price)
    {
        $_price_array = RedPacketService::redpackRandSplit($price, $num);
        $redis = new Redis();
        foreach ($_price_array as $k => $v) {
            $redis->rPush("redpack_detail" . $redpack_id, $v);
        }
        return true;
    }

    /**
     * @param $redpack_id 红包id
     * @param $member_group  接收人code数组
     * @return bool
     * @context 存储红包接收人的关系
     */
    function setRedPacketMemberMapping($redpack_id, $member_group)
    {
        $redis = new Redis();
        $redis->hSetJson("redpack_mapping", $redpack_id, $member_group);
        return true;
    }

    /**
     * @param $redpack_id
     * @return bool|mixed
     * @context 获取红包接收人的关系
     */
    function getRedPacketMemberMapping($redpack_id)
    {
        $redis = new Redis();
        return $redis->hGetJson("redpack_mapping", $redpack_id);
    }

    /**
     * @param $redpack_id
     * @return bool
     * @context 设置红包领取关系过期时间，用户红包过期时间
     */
    function setExperition($redpack_id)
    {
        $_time = date("Y-m-d H:i:s", strtotime("+2 hours"));
        $member_group = RedPacketService::getRedPacketMemberMapping($redpack_id);
        $redis = new Redis();
        # 设置红包领取关系过期时间
        $redis->hSetJson("redpack_mapping", $redpack_id, $member_group, 7200);
        # 设置每个人这个红包的过期时间
        foreach ($member_group as $k => $v) {
            $list = $redis->hGet("redpack_member" . $v, $redpack_id);
            if (!empty($list)) {
                $list = json_decode($list, true);
                $list['expiration'] = $_time;
                $list = json_encode($list);
                $redis->hSet("redpack_member" . $v, $redpack_id, $list);
            }
        }
        return true;
    }

    /**
     * @param $price  总金额
     * @param $num    总数量
     * @return array
     * @context 红包金额拆分
     */
    function redpackRandSplit($price, $num)
    {
        $_total = $price;
        $_plurs = $_total;
        $min = 1;
        $max_standard = intval($price / $num) * 3;
        $_return = array();
        for ($i = 1; $i <= $num; $i++) {
            if ($i == $num) {
                $money = $_plurs;
            } else {
                $max = $_plurs - (($num - $i) * $min);
                if ($max > $max_standard) {
                    $max = $max_standard;
                }
                $money = rand($min, $max);
            }
            array_push($_return, $money);
            $_plurs -= $money;
        }
        shuffle($_return);
        return $_return;
    }

    static $rewardMoney;        #红包金额、单位元
    static $rewardNum;          #红包数量

    #执行红包生成算法
    public function splitReward($rewardMoney, $rewardNum, $max, $min)
    {
        #传入红包金额和数量，因为小数在计算过程中会出现很大误差，所以我们直接把金额放大100倍，后面的计算全部用整数进行
        $min = $min * 100;
        $max = $max * 100;
        #预留出一部分钱作为误差补偿，保证每个红包至少有一个最小值
        self::$rewardMoney = $rewardMoney * 100 - $rewardNum * $min;
        self::$rewardNum = $rewardNum;
        #计算出发出红包的平均概率值、精确到小数4位。
        $avgRand = 1 / self::$rewardNum;
        $randArr = array();
        #定义生成的数据总合sum
        $sum = 0;
        $t_count = 0;
        while ($t_count < $rewardNum) {
            #随机产出四个区间的额度
            $c = rand(1, 100);
            if ($c < 15) {
                $t = round(sqrt(mt_rand(1, 1500)));
            } else if ($c < 65) {
                $t = round(sqrt(mt_rand(1500, 6500)));
            } else if ($c < 95) {
                $t = round(sqrt(mt_rand(6500, 9500)));
            } else {
                $t = round(sqrt(mt_rand(9500, 10000)));
            }
            ++$t_count;
            $sum += $t;
            $randArr[] = $t;
        }

        #计算当前生成的随机数的平均值，保留4位小数
        $randAll = round($sum / $rewardNum, 4);

        #为将生成的随机数的平均值变成我们要的1/N，计算一下每个随机数要除以的总基数mixrand。此处可以约等处理，产生的误差后边会找齐
        #总基数 = 均值/平均概率
        $mixrand = round($randAll / $avgRand, 4);

        #对每一个随机数进行处理，并乘以总金额数来得出这个红包的金额。
        $rewardArr = array();
        foreach ($randArr as $key => $randVal) {
            #单个红包所占比例randVal
            $randVal = round($randVal / $mixrand, 4);
            #算出单个红包金额
            $single = floor(self::$rewardMoney * $randVal);
            #小于最小值直接给最小值
            if ($single < $min) {
                $single += $min;
            }
            #大于最大值直接给最大值
            if ($single > $max) {
                $single = $max;
            }
            #将红包放入结果数组
            $rewardArr[] = $single;
        }

        #对比红包总数的差异、将差值放在第一个红包上
        $rewardAll = array_sum($rewardArr);
        $rewardArr[0] = $rewardMoney * 100 - ($rewardAll - $rewardArr[0]);#此处应使用真正的总金额rewardMoney，$rewardArr[0]可能小于0

        #第一个红包小于0时,做修正
        if ($rewardArr[0] < 0) {
            rsort($rewardArr);
            RedPacketService::add($rewardArr, $min);
        }

        rsort($rewardArr);
        #随机生成的最大值大于指定最大值
        if ($rewardArr[0] > $max) {
            #差额
            $diff = 0;
            foreach ($rewardArr as $k => &$v) {
                if ($v > $max) {
                    $diff += $v - $max;
                    $v = $max;
                } else {
                    break;
                }
            }
            $transfer = round($diff / (self::$rewardNum - $k + 1));
            RedPacketService::diff($diff, $rewardArr, $max, $min, $transfer, $k);
        }
        return $rewardArr;
    }

    #处理所有超过最大值的红包
    public function diff($diff, &$rewardArr, $max, $min, $transfer, $k)
    {
        #将多余的钱均摊给小于最大值的红包
        for ($i = $k; $i < self::$rewardNum; $i++) {
            #造随机值
            if ($transfer > $min * 20) {
                $aa = rand($min, $min * 20);
                if ($i % 2) {
                    $transfer += $aa;
                } else {
                    $transfer -= $aa;
                }
            }
            if ($rewardArr[$i] + $transfer > $max) continue;
            if ($diff - $transfer < 0) {
                $rewardArr[$i] += $diff;
                $diff = 0;
                break;
            }
            $rewardArr[$i] += $transfer;
            $diff -= $transfer;
        }
        if ($diff > 0) {
            $i++;
            RedPacketService::diff($diff, $rewardArr, $max, $min, $transfer, $k);
        }
    }

    #第一个红包小于0,从大红包上往下减
    public function add(&$rewardArr, $min)
    {
        foreach ($rewardArr as &$re) {
            $dev = floor($re / $min);
            if ($dev > 2) {
                $transfer = $min * floor($dev / 2);
                $re -= $transfer;
                $rewardArr[self::$rewardNum - 1] += $transfer;
            } elseif ($dev == 2) {
                $re -= $min;
                $rewardArr[self::$rewardNum - 1] += $min;
            } else {
                break;
            }
        }
        if ($rewardArr[self::$rewardNum - 1] > $min || $rewardArr[self::$rewardNum - 1] == $min) {
            return;
        } else {
            RedPacketService::add($rewardArr, $min);
        }
    }

//    public function random_red($total, $num, $max, $min)
//    {
//        #总共要发的红包金额，留出一个最大值;
//        $total = $total - $max;
//        $reward = new RedPacketService();
//        $result_merge = $reward->splitReward($total, $num, $max - 0.01, $min);
//        sort($result_merge);
//        $result_merge[1] = $result_merge[1] + $result_merge[0];
//        $result_merge[0] = $max * 100;
//        foreach ($result_merge as &$v) {
//            $v = floor($v) / 100;
//        }
//        return $result_merge;
//    }

    /**
     * @throws \think\Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * @throws \think\exception\PDOException
     * @content 24小时未领取完的红包自动退回至账号
     */
    function autoRollBack()
    {
        $redis = new Redis();
        # 判断10分钟内是否操作做
        $rollBack = $redis->get('rollBack');
        if (empty($rollBack)) {
            # 查询超过24小时未领取完的红包,给人家把剩下的钱退回去啦
            $info = Db::table('redpacket')
                ->field('id,member_code,pack_type,pack_value,(select sum(price) from log_redpacket where packet_id=redpacket.id) receivedPrice')
                ->where(array('plurs_num' => 0, 'is_return' => 1))
                ->where("create_time <= '" . date('Y-m-d H:i:s', strtotime('-1 day')) . "'")
                ->select();
            if (!empty($info)) {
                foreach ($info as $k => $v) {
                    $memberInfo = $redis->hGetJson('memberInfo', $v['member_code']);
                    $_price = $v['pack_value'] - $v['receivedPrice'];
                    if ($_price > 0) {
                        if ($v['pack_type'] == 1) {
                            #  1 金币红包
                            Db::table('member_property')->where(array('member_code' => $v['member_code']))->setInc('gold', $_price);
                            MemberService::addLog_gold($v['member_code'], $_price, 19);
                            # 更新redis
                            $memberInfo['gold'] = priceFormat($memberInfo['gold'] + $_price);
                        } else {
                            # 2 积分红包
                            Db::table('member_property')->where(array('member_code' => $v['member_code']))->setInc('integral', $_price);
                            MemberService::addLog_integral($v['member_code'], $_price, 1, 15);
                            # 更新redis
                            $memberInfo['integral'] = priceFormat($memberInfo['integral'] + $_price);
                        }
                        $redis->hSetJson('memberInfo', $v['member_code'], $memberInfo);
                        # 修改红包为已经退回状态
                        Db::table('redpacket')->where(array('id' => $v['id']))->update(array('is_return' => 1));
                    }
                }
            }
            # 存redis , 每10分钟执行一次
            $redis->set('rollBack', 'isReturn', 600);
        }
    }


}